package org.clockwise.srw;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.clockwise.driver.LinkerDriver;
import org.clockwise.multimethod.PageRank;
import org.clockwise.util.MyFileWriter;

public class AccountRelationPool {

	public static final String NETWORK_DIR = LinkerDriver.DATA_DIR + "fan/";

	public static final String SOURCE = LinkerDriver.DATA_DIR
			+ "AccountRelation";
	public static HashMap<Long, HashMap<Long, Double>> relation = null;
	public static MyFileWriter mfw = null;
	public static boolean initDone = false;

	private static double calRelation(int max, int min, int common) {
//		return ((double) common) / (max + min - common);
		
		return min;
//		return min;
		// assert (max > common);
		// return (Math.log(12000000) - Math.log(min)) / (Math.log(max) -
		// Math.log(common));
	}

	public static void init() {
		String file = SOURCE;
		relation = new HashMap<Long, HashMap<Long, Double>>();
		try {
			BufferedReader reader = new BufferedReader(new FileReader(file));
			String temp = null;
			while ((temp = reader.readLine()) != null) {
				String[] seg = temp.split("\t");
				if (seg.length != 5) {
					continue;
				}
				try {
					Long id1 = Long.parseLong(seg[0]);
					Long id2 = Long.parseLong(seg[1]);
					int max = Integer.parseInt(seg[2]);
					int min = Integer.parseInt(seg[3]);
					int common = Integer.parseInt(seg[4]);

					// double sr = (Math.log(max) - Math.log(common)) /
					// (Math.log(2000) - Math.log(min));
					// sr = Math.exp(-sr);

					Double r = calRelation(max, min, common);
					insert(id1, id2, r);

				} catch (NumberFormatException e) {
					// System.out.println("Error! Wrong number format:" + temp);
				}
			}
			reader.close();
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			System.out.println("Cannot find file \"AccountRelation\"");

		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			mfw = new MyFileWriter(SOURCE, false);
			initDone = true;
		}
	}

	public static void record(Long id1, Long id2, int max, int min, int common) {
		if (initDone) {
			if (mfw == null) {
				mfw = new MyFileWriter(SOURCE, false);
			}
			mfw.write(id1.toString() + "\t" + id2.toString() + "\t" + max
					+ "\t" + min + "\t" + common + "\n");
		}
	}

	public static void record(Long id1, Long id2, Double r) {
		if (initDone) {
			if (mfw == null) {
				mfw = new MyFileWriter(SOURCE, false);
			}
			mfw.write(id1.toString() + "\t" + id2.toString() + "\t"
					+ r.toString() + "\n");
			// System.out.println("Recorded");
		}
		insert(id1, id2, r);
	}

	private static Double find(Long id1, Long id2) {
		if (relation.containsKey(id1)) {
			HashMap<Long, Double> dict = relation.get(id1);
			if (dict.containsKey(id2)) {
				return dict.get(id2);
			}
		}
		if (relation.containsKey(id2)) {
			HashMap<Long, Double> dict = relation.get(id2);
			if (dict.containsKey(id1)) {
				return dict.get(id1);
			}
		}
		return null;
	}

	public static void insert(Long id1, Long id2, Double r) {
		if (relation.containsKey(id1)) {
			relation.get(id1).put(id2, r);
		} else if (relation.containsKey(id2)) {
			relation.get(id2).put(id1, r);
		} else {
			HashMap<Long, Double> dict = new HashMap<Long, Double>();
			dict.put(id2, r);
			relation.put(id1, dict);
		}
	}

	public static Double evaluate(Long id1, Long id2) {

		// MyFileWriter mfw = new MyFileWriter("tobe", false);
		// mfw.write(id1.toString() + "\t" + id2.toString() + "\n");
		// mfw.close();
		// return 0.0;

		// System.out.println("Evaluating " + id1.toString() + " and " +
		// id2.toString());
		if (relation == null) {
			init();
		}

		if (id1.equals(id2)) {
			return new Double(0.0);
		}

		Double result = find(id1, id2);
		if (result != null) {
			return result;
		}

		// System.out.println("Canno find relation of " + id1.toString() +
		// " and " + id2.toString());

		HashSet<Long> fans1 = new HashSet<Long>();
		HashSet<Long> fans2 = new HashSet<Long>();

		String file = null;
		fillSet(fans1, id1);
		fillSet(fans2, id2);
		if (fans1.size() == 0 || fans2.size() == 0) {
			return new Double(0.0);
		}

		HashSet<Long> more = fans1.size() > fans2.size() ? fans1 : fans2;
		HashSet<Long> less = fans1.size() <= fans2.size() ? fans1 : fans2;
		int max = more.size();
		int min = less.size();
		int common = 0;
		for (Long id : less) {
			if (more.contains(id)) {
				common++;
			}
		}
		if (common == 0) {
			common = 1;
		}
		// System.out.println("max = " + max + ", min = " + min + ", common = "
		// + common);

		// the evaluation function is from paper:
		// Collective Entity Linking in Web Text A Graph-Based Method, 2011
		// SIGIR

		// double sr = (Math.log(max) - Math.log(common)) / (Math.log(2000) -
		// Math.log(min));
		// sr = Math.exp(-sr);

		/** This is the original version. */
		// double sr = ((double) common) / (max + min - common);

		max = 0;
		min = 0;
		common = 0;
		if (fans1.contains(id2) && fans2.contains(id1)) {
			max = 1;
		}
		if (fans1.contains(id2) || fans2.contains(id1)) {
			min = 1;
		}

//		 double sr = ((double)common) * 2 / (max + min);
		double sr = AccountRelationPool.calRelation(max, min, common);
		insert(id1, id2, sr);
		record(id1, id2, max, min, common);
		// System.out.println("Evaluation done with result " + sr);
		return new Double(sr);

	}

	private static HashSet<Long> getIdSet(String line) {
		HashSet<Long> set = new HashSet<Long>();
		String[] seg = line.split(",");
		for (int i = 0; i < seg.length; i++) {
			try {
				Long id = Long.parseLong(seg[i]);
				set.add(id);
			} catch (NumberFormatException e) {
				// do nothing
			}
		}
		return set;
	}

	private static void fillSet(Set<Long> set, Long id) {
		if (set == null) {
			set = new HashSet<Long>();
		}
		if (id == null) {
			System.out.println("id is null when calling fillSet()");
		}

		String file = NETWORK_DIR + id.toString();
		try {
			BufferedReader reader = new BufferedReader(new FileReader(file));
			String temp = null;
			while ((temp = reader.readLine()) != null) {
				String[] seg = temp.split("\t");
				for (int i = 0; i < seg.length; i++) {
					try {
						Long l = Long.parseLong(seg[i]);
						set.add(l);
					} catch (NumberFormatException e) {
						// do nothing
					}
				}
			}
		} catch (FileNotFoundException e) {
			// System.out.println("Error! Cannot find file " + file);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public static void getRelations(List<List<List<double[]>>> data,
			List<List<List<Long>>> dict, List<double[][]> feature,
			List<double[][]> relation, List<int[]> ruler) {
		Iterator<List<List<double[]>>> cqIter = data.iterator();
		Iterator<List<List<Long>>> cuIter = dict.iterator();
		while (cqIter.hasNext() && cuIter.hasNext()) {
			List<List<double[]>> context = cqIter.next();
			List<List<Long>> cUser = cuIter.next();

			int dim = 0;

			// the start position of each query
			int[] start = new int[context.size() + 1];

			start[0] = 0;
			int q = 1;
			List<Long> idlist = new ArrayList<Long>();
			Iterator<List<double[]>> queryIter = context.iterator();
			Iterator<List<Long>> qUserIter = cUser.iterator();
			List<double[]> allFeature = new ArrayList<double[]>();
			while (queryIter.hasNext() && qUserIter.hasNext()) {
				List<double[]> query = queryIter.next();
				List<Long> qUser = qUserIter.next();
				dim += query.size();
				start[q] = start[q - 1] + query.size();
				q++;
				Iterator<double[]> featureIter = query.iterator();
				Iterator<Long> userIter = qUser.iterator();
				while (featureIter.hasNext() && userIter.hasNext()) {
					allFeature.add(featureIter.next());
					idlist.add(userIter.next());

				}
				if (featureIter.hasNext() || userIter.hasNext()) {
					System.out
							.println("Fatal Error! feature list and user list do not match!");
				}
			}

			if (queryIter.hasNext() || qUserIter.hasNext()) {
				System.out.println("Fatal Error! data and dict do not match");
			}

			Long[] id = new Long[idlist.size()];
			idlist.toArray(id);
			double[][] cf = new double[allFeature.size()][];
			allFeature.toArray(cf);
			feature.add(cf);

			// initialize the relation matrix
			double[][] matrix = new double[dim][];

//			System.out.println("dim = " + dim);

			for (int i = 0; i < dim; i++) {
				matrix[i] = new double[dim];
			}
			for (int i = 0; i < dim; i++) {
				for (int j = i + 1; j < dim; j++) {
					double r = AccountRelationPool.evaluate(id[i], id[j]);
					matrix[i][j] = r;
					matrix[j][i] = r;
				}
			}

			matrix = PageRank.normalizeByRow(matrix);

			// System.out.println("Get relation matrix : " + dim);
			relation.add(matrix);
			ruler.add(start);

		}
		if (cqIter.hasNext() || cuIter.hasNext()) {
			System.out
					.println("Fatal Error! Dimension unmatched in getRelations()!");
		}

	}
}
